#include "LuaScriptLogic.h"

#include "Exception.h"
#include "swigluarun.h"
#include "Logger.h"
#include <iostream>

extern "C" {
    int luaopen_core(lua_State* L);
    int luaopen_input(lua_State* L);
    int luaopen_vect(lua_State* L);
    int luaopen_printing(lua_State* L);
    int luaopen_audio(lua_State* L);
}

namespace EntitySystem
{
    
const char *LuaScriptLogic::METHOD_TABLE[] = 
{
    "Initialize",
    "Update",
    "OnTileCollision",
    "OnEntityCollision",
    "OnKeyButtonDown",
    "OnKeyButtonUp",
    "OnDeath"
};


LuaScriptLogic::LuaScriptLogic(const std::string &script)
{
    Initialize( script );
}


LuaScriptLogic::~LuaScriptLogic()
{
    if( m_context )
        lua_close( m_context );
}

/**
 * This logic should not be cloned or copied, thus the invocation
 * of Clone should throw an exception.
 */

ScriptLogic* LuaScriptLogic::Clone()
{
    throw Exception( "Tried to clone a LuaScriptLogic object!" );
}

/**
 * Executes the Initialize function from script.
 */

void LuaScriptLogic::ExecuteInitialize()
{    
    static int methodIndex = 0;
    
    if( m_methodDefinitions[methodIndex] )  
    {
	int err = 0;
	
	lua_pushstring( m_context, METHOD_TABLE[methodIndex] );
	
	lua_gettable( m_context, LUA_GLOBALSINDEX );
	
	if( ( err = lua_pcall( m_context, 0, 0, 0 )  ) != 0  )
	{
	    PrintExecError( methodIndex, err );   
	}
    }
}


/**
 * Executes the Update function from script.
 */
    
void LuaScriptLogic::ExecuteUpdate()
{   
    static int methodIndex = 1;
    
    if( m_methodDefinitions[methodIndex] )
    {
	int err = 0;
	
        lua_pushstring( m_context, METHOD_TABLE[methodIndex] );
	
	lua_gettable( m_context, LUA_GLOBALSINDEX );
	
	if( ( err = lua_pcall( m_context, 0, 0, 0 )  ) != 0  )
	{
	    PrintExecError( methodIndex, err );   
	}
    }
}


/**
 * Executes the OnTileCollision function from script.
 *
 * @param tile: string that indicates the type of the tile that collided with the script owner.
 * @param contactNormal: vector indicating the direction of the collision relative to the owner
 *                       of this script.
 */

void LuaScriptLogic::ExecuteOnTileCollision(std::string* tile, Math::Vector2F& contactNormal)
{
    static int methodIndex = 2;
    
    if( m_methodDefinitions[methodIndex] )
    {
	int err = 0;
	
        lua_pushstring( m_context, METHOD_TABLE[methodIndex] );
	
	lua_gettable( m_context, LUA_GLOBALSINDEX );
	
	lua_pushstring( m_context, tile->c_str( ) );
	
	SWIG_NewPointerObj( m_context, &contactNormal, SWIG_TypeQuery( m_context, "Math::Vector2Templ< float >*" ), 0 );
	
	if( ( err = lua_pcall( m_context, 2, 0, 0 )  ) != 0  )
	{
	    PrintExecError( methodIndex, err );   
	}
    }    
}


/**
 * Executes the OnEntityCollision function from script.
 *
 * @param collided: entity that collided with the owner of this script.
 * @param contactNormal: vector indicating the direction of the collision relative
 *                       to the owner of this script.
 */

void LuaScriptLogic::ExecuteOnEntityCollision(Entity* collided, Math::Vector2F& contactNormal)
{
    static int methodIndex = 3;
    
    if( m_methodDefinitions[methodIndex] )
    {
	int err = 0;
	
        lua_pushstring( m_context, METHOD_TABLE[methodIndex] );
	
	lua_gettable( m_context, LUA_GLOBALSINDEX );
	
	SWIG_NewPointerObj( m_context, collided, SWIG_TypeQuery( m_context, "EntitySystem::Entity*" ), 0);
	
	SWIG_NewPointerObj( m_context, &contactNormal, SWIG_TypeQuery( m_context, "Math::Vector2Templ< float >*" ), 0);
	
	if( ( err = lua_pcall( m_context, 2, 0, 0 )  ) != 0  )
	{
	    PrintExecError( methodIndex, err );
	}
    }  
}


/**
 * Executes the OnKeyButtonDown function from script.
 *
 * @param key: key pressed.
 * @param mod: modifiers applied to the key pressed.
 * @param association: association to the key+modidiers.
 */

void LuaScriptLogic::ExecuteOnKeyButtonDown(Key key, KeyMod mod, std::string& association)
{
    static int methodIndex = 4;
    
    if( m_methodDefinitions[methodIndex] )
    {
	int err = 0;
	
	lua_pushstring( m_context, METHOD_TABLE[methodIndex] );
	
	lua_gettable( m_context, LUA_GLOBALSINDEX );
	
        lua_pushnumber( m_context, key );
	
	lua_pushnumber( m_context, mod );
	
	lua_pushstring( m_context, association.c_str( ) );
	
	if( ( err = lua_pcall( m_context, 3, 0, 0 )  ) != 0  )
	{
	    PrintExecError( methodIndex, err );
	}
    } 
}


/**
 * Executes the OnKeyButtonUp function from script.
 *
 * @param key: key released.
 * @param mod: modifiers applied to the key released.
 * @param association: association to the key+modidiers.
 */

void LuaScriptLogic::ExecuteOnKeyButtonUp(Key key, KeyMod mod, std::string& association)
{
    static int methodIndex = 5;
    
    if( m_methodDefinitions[methodIndex] )
    {
	int err = 0;
	
        lua_pushstring( m_context, METHOD_TABLE[methodIndex] );
	
	lua_gettable( m_context, LUA_GLOBALSINDEX );
	
        lua_pushnumber( m_context, key );
	
	lua_pushnumber( m_context, mod );
	
	lua_pushstring( m_context, association.c_str( ) );
	
	if( ( err = lua_pcall( m_context, 3, 0, 0 )  ) != 0  )
	{
	    PrintExecError( methodIndex, err );
	}
    }  
}


/**
 * Executes the OnDeath function from script.
 */

void LuaScriptLogic::ExecuteOnDeath()
{
    static int methodIndex = 6;
    
    if( m_methodDefinitions[methodIndex] )
    {
	int err = 0;
	
        lua_pushstring( m_context, METHOD_TABLE[methodIndex] );
	
	lua_gettable( m_context, LUA_GLOBALSINDEX );
	
	if( ( err = lua_pcall( m_context, 0, 0, 0 )  ) != 0  )
	{
            PrintExecError( methodIndex, err );
	}
    }  
}


/**
 * Sets the parent of this script logic. Adds a global Entity object to the lua 
 * context named "this".
 */

void LuaScriptLogic::SetParent(Entity *parent)
{
    ScriptLogic::SetParent( parent );
    
    SWIG_NewPointerObj( m_context, parent, SWIG_TypeQuery( m_context, "EntitySystem::Entity*" ), 0 );
    
    lua_setglobal( m_context, "this" );
}



/**
 * Pushes a string object to the script logic's state.
 *
 * @param name: name of the object's reference.
 * @param value: value of the object.
 */

void LuaScriptLogic::PushString(const std::string& name, const std::string& value)
{
    lua_pushstring( m_context, value.c_str( ) );
    
    lua_setglobal( m_context, name.c_str( ) );
}


/**
 * Pushes an object reference to the script logic's state.
 *
 * @param name: name of the reference.
 * @param object: object to push.
 * @param type: type of the object given as a string.
 * @param engineManaged: true if the object is managed by the engine, false otherwise.
 */

void LuaScriptLogic::PushObject(const std::string& name, void* object, const std::string& type, bool engineManaged)
{   
    SWIG_NewPointerObj( m_context, object, SWIG_TypeQuery( m_context, type.c_str( ) ), engineManaged? 0 : 1 );
    
    lua_setglobal( m_context, name.c_str( ) );
}


const char* LuaScriptLogic::GetName()
{
    return m_name.c_str( );
}



/**
 * Opens the Lua context , loads the chunk of code specified and opens the lua modules needed
 * by the scripting enivornment.
 */

void LuaScriptLogic::Initialize(const std::string& scriptPath)
{
    int err;
    
    try
    {
        //open a lua context
        m_context = lua_open( );
	
	//load file and check for erros
	if( (err = luaL_loadfile( m_context, scriptPath.c_str( ) ) ) != 0 )
	{
	    
	    if( err == LUA_ERRSYNTAX )
	    {
		LOG( Logger::CHANNEL_SCRIPTING, LogFileStream::LEVEL_ERROR ) << "Syntax error in file: " << scriptPath << "\n";
	    }
	    else if( err == LUA_ERRMEM )
	    {
		LOG( Logger::CHANNEL_MAIN, LogFileStream::LEVEL_ERROR ) << "Cannot allocate memory for script: " << scriptPath << "\n";
	    }
	    else if( err == LUA_ERRFILE )
	    {
		LOG( Logger::CHANNEL_LOADING, LogFileStream::LEVEL_ERROR ) << "Lua script file missing: " << scriptPath << "\n";
	    }
	}
    
        //open libraries for scripting
        luaL_openlibs( m_context );
        luaopen_core( m_context );
	luaopen_input( m_context );
	luaopen_vect( m_context );
	luaopen_printing( m_context );
        luaopen_audio( m_context );
    }
    catch( char const* exc )
    {
	throw Exception( ( std::string( "Can't create Lua Script Logic, reason: " ) + exc ).c_str( ) );
    } 
    
    //call script to initialize script's global variables
    lua_pcall( m_context, 0, LUA_MULTRET, 0 );
    
    
    //check which functions are defined in this script
    CheckMethodDefinitions( );
    
    //get the script's name only
    int lastSlash = scriptPath.rfind( '/' );
    
    m_name = scriptPath.substr( lastSlash + 1, ( scriptPath.length( ) - lastSlash ) );
}


/**
 * Checks which methods have been defined in a lua script.
 */

void LuaScriptLogic::CheckMethodDefinitions()
{
    for( int i = 0; i < N_METHODS; ++i )
    {
	lua_getglobal( m_context, METHOD_TABLE[i] );
	
	m_methodDefinitions[i] = lua_isfunction( m_context, lua_gettop( m_context ) ) ? true : false;
	
	lua_setglobal( m_context, METHOD_TABLE[i] );
    }
}


/**
 * Prints an error occurred when executing a script function. 
 *
 * @param methodIndex: index (in METHOD_TABLE) of the function that failed to execute.
 * @param errorCode: error code returned by lua.
 */

void LuaScriptLogic::PrintExecError(int methodIndex, int errorCode)
{
    LOG( Logger::CHANNEL_SCRIPTING, LogFileStream::LEVEL_ERROR ) << "Could not invoke " << METHOD_TABLE[methodIndex] << " in a Entity script! Reason: ";
    
    const char *error = lua_tostring( m_context, lua_gettop( m_context ) );

    if( errorCode == LUA_ERRRUN )
    {
	LOG( Logger::CHANNEL_SCRIPTING, LogFileStream::LEVEL_ERROR ) << " Runtime Error: " << error << '\n';
    }
    else if( errorCode == LUA_ERRMEM )
    {
	LOG( Logger::CHANNEL_SCRIPTING, LogFileStream::LEVEL_ERROR ) << " Memory Error: " << error << '\n';
    }
    else
    {
	LOG( Logger::CHANNEL_SCRIPTING, LogFileStream::LEVEL_ERROR ) << " Unknown Error: " << error << '\n';
    }
}



/**
 * Default constructor, initializes the lua context to null.
 */

LuaScriptLogic::LuaScriptLogic()
{
    m_context = NULL;    
}



};